package com.ruoyi.system.service.impl;

import java.util.*;
import java.util.concurrent.*;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.regex.Pattern;
import java.util.regex.Matcher;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.system.domain.VideoData;
import com.ruoyi.system.domain.VideoStream;
import com.ruoyi.system.service.IVideoDataService;
import com.ruoyi.system.service.IVideoSaveService;
import com.ruoyi.system.service.IVideoStreamService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 视频采集Service业务层处理 - 优化最终化版本
 */
@Service
public class VideoSaveServiceImpl implements IVideoSaveService {
    private static final Logger log = LoggerFactory.getLogger(VideoSaveServiceImpl.class);

    // 使用线程池管理所有线程
    private final ExecutorService executorService = Executors.newCachedThreadPool();
    // 最终化专用线程池
    private final ExecutorService finalizationExecutor = Executors.newCachedThreadPool();
    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");

    @Autowired
    private IVideoDataService videoDataService;
    @Autowired
    private IVideoStreamService videoStreamService;

    @Value(value = "${ffmpeg.path}")
    private String ffmpegPath;
    @Value(value = "${ffmpeg.savePath}")
    private String savePath;
    @Value(value = "${ffmpeg.fileNoGrowthTimeout}")
    private int fileNoGrowthTimeout;
    @Value(value = "${ffmpeg.maxRetryCount}")
    private int maxRetryCount;
    @Value(value = "${ffmpeg.videoCodec}")
    private String videoCodec;
    @Value(value = "${ffmpeg.audioCodec}")
    private String audioCodec;
    @Value(value = "${ffmpeg.crf}")
    private int crf;
    @Value(value = "${ffmpeg.preset}")
    private String preset;
    @Value(value = "${ffmpeg.gop}")
    private int gop;
    @Value(value = "${ffmpeg.keyintMin}")
    private int keyintMin;
    @Value(value = "${ffmpeg.scThreshold}")
    private int scThreshold;
    @Value(value = "${ffmpeg.analyzeDuration}")
    private int analyzeDuration;
    @Value(value = "${ffmpeg.probeSize}")
    private int probeSize;

    // 使用ConcurrentHashMap存储进程信息
    private static final ConcurrentHashMap<String, ProcessInfo> processMap = new ConcurrentHashMap<>();
    // 最终化状态跟踪
    private static final ConcurrentHashMap<String, ProcessFinalizationInfo> finalizationMap = new ConcurrentHashMap<>();

    // 最终化状态枚举
    private enum ProcessFinalizationState {
        PENDING, PROCESSING, COMPLETED, FAILED, CANCELLED
    }

    /**
     * 清理文件名中的特殊字符，确保文件路径有效
     */
    private String cleanFileName(String fileName) {
        if (fileName == null || fileName.trim().isEmpty()) {
            return "unnamed";
        }
        // 移除或替换不允许的字符
        String safeName = fileName.trim()
                .replaceAll("[<>:\"/\\|?*\']", "_") // 替换Windows不允许的字符
                .replaceAll("\\s+", "_") // 替换连续空格
                .replaceAll("_+", "_") // 替换连续下划线
                .replaceAll("^_+", "") // 移除开头的下划线
                .replaceAll("_+$", ""); // 移除结尾的下划线

        // 限制文件名长度
        if (safeName.length() > 100) {
            safeName = safeName.substring(0, 100);
        }

        return safeName.isEmpty() ? "unnamed" : safeName;
    }

    /**
     * 验证URL是否有效
     */
    private boolean isValidUrl(String url) {
        if (url == null || url.trim().isEmpty()) {
            return false;
        }
        try {
            // 检查URL格式是否基本正确
            if (!url.toLowerCase().startsWith("http://") &&
                    !url.toLowerCase().startsWith("https://") &&
                    !url.toLowerCase().startsWith("rtsp://") &&
                    !url.toLowerCase().startsWith("rtmp://")) {
                return false;
            }

            // 检查URL是否包含基本的格式要求
            if (url.contains("://") && url.indexOf("://") < url.length() - 3) {
                return true;
            }
            return false;
        } catch (Exception e) {
            log.warn("URL验证异常: {}", e.getMessage());
            return false;
        }
    }

    // 进程信息内部类
    private static class ProcessInfo {
        Process process;
        String outputPath;
        String tempOutputPath;
        VideoStream videoStream;
        long lastFileSize;
        long lastModifiedTime;
        int retryCount;
        String threadID;
        boolean isFinalizing;
        ProcessFinalizationState finalizationState;
        String finalizationMessage;
        double finalizationProgress;
        CompletableFuture<Boolean> finalizationFuture;

        ProcessInfo(Process process, String outputPath, String tempOutputPath, VideoStream videoStream,
                String threadID) {
            this.process = process;
            this.outputPath = outputPath;
            this.tempOutputPath = tempOutputPath;
            this.videoStream = videoStream;
            this.lastFileSize = 0;
            this.lastModifiedTime = System.currentTimeMillis();
            this.retryCount = 0;
            this.threadID = threadID;
            this.isFinalizing = false;
            this.finalizationState = ProcessFinalizationState.PENDING;
            this.finalizationMessage = "";
            this.finalizationProgress = 0;
        }
    }

    // 最终化信息内部类
    private static class ProcessFinalizationInfo {
        String threadID;
        ProcessFinalizationState state;
        String message;
        double progress;
        LocalDateTime startTime;
        LocalDateTime endTime;
        String tempFilePath;
        String finalFilePath;

        ProcessFinalizationInfo(String threadID, String tempFilePath, String finalFilePath) {
            this.threadID = threadID;
            this.state = ProcessFinalizationState.PENDING;
            this.message = "等待最终化";
            this.progress = 0;
            this.startTime = null;
            this.endTime = null;
            this.tempFilePath = tempFilePath;
            this.finalFilePath = finalFilePath;
        }
    }

    @Override
    public AjaxResult startSave(String streamIds,Integer collectionTaskId,String collectionTaskName) {

        try {

            if (streamIds == null || streamIds.isEmpty()) {
                return AjaxResult.error("视频流ID不能为空");
            }

            String[] streamIdArray = streamIds.split(",");
            List<String> streamIdList = Arrays.asList(streamIdArray);

            List<VideoStream> videoStreamList = new ArrayList<>();
            if (streamIdList == null || streamIdList.isEmpty()) {
                videoStreamList = videoStreamService.getSaveVideoStreamList();
            } else {
                videoStreamList = videoStreamService.selectVideoStreamListByIds(streamIdList);
            }

            if (videoStreamList == null || videoStreamList.isEmpty()) {
                return AjaxResult.error("没有待采集的视频流");
            }

            int successCount = 0;
            StringBuilder errorMessages = new StringBuilder();
            // 创建输出目录
            String dateStr = LocalDateTime.now().format(DATE_FORMATTER);
            String timeStr = LocalDateTime.now().format(TIME_FORMATTER);
            String outputPathDirectory = savePath + "/" + dateStr + "/" + timeStr;

            for (VideoStream videoStream : videoStreamList) {
                try {
                    if (ffmpegStartSave(videoStream, outputPathDirectory)) {

                        successCount++;
                        log.info("成功启动视频流采集: {}", videoStream.getStreamTitle());
                    }
                } catch (Exception e) {
                    String errorMsg = String.format("视频流 %s 启动失败: %s",
                            videoStream.getStreamTitle(), e.getMessage());
                    log.error(errorMsg, e);
                    errorMessages.append(errorMsg).append("; ");
                }
            }

            String resultMsg = String.format("成功启动%d个视频采集任务", successCount);
            if (errorMessages.length() > 0) {
                resultMsg += ", 失败详情: " + errorMessages.toString();
            }

            if (successCount > 0) {
                VideoData videoData = new VideoData();
                videoData.setStartCollectTime(LocalDateTime.now());
                //这里采用相对路径，用java访问静态资源的方式，映射路径，方法为addResourceHandlers
                outputPathDirectory = File.separator + dateStr + File.separator + timeStr;
                videoData.setStoragePath(outputPathDirectory);
                videoData.setRemark("采集了 " + videoStreamList.size() + " 路视频，");
                videoData.setCollectionTaskId(collectionTaskId);
                videoData.setCollectionTaskName(collectionTaskName);
                videoDataService.insertVideoData(videoData);

                return AjaxResult.success(resultMsg).put("dataId", videoData.getDataId());
            }

            return AjaxResult.error(resultMsg);

        } catch (Exception e) {
            log.error("采集启动失败", e);
            return AjaxResult.error("采集启动失败: " + e.getMessage());
        }
    }

    private boolean ffmpegStartSave(VideoStream videoStream, String outputPathDirectory) {

        String threadID = UUID.randomUUID().toString();
        String captureUrl = videoStream.getCaptureUrl();
        String streamTitle = videoStream.getStreamTitle();

        // 清理标题中的特殊字符，确保文件路径有效
        String safeStreamTitle = cleanFileName(streamTitle);

        try {
            // 验证并创建输出目录
            File outputDir = new File(outputPathDirectory);
            if (!outputDir.exists()) {
                boolean created = outputDir.mkdirs();
                if (!created) {
                    // 再次检查，因为可能其他进程已经创建了该目录
                    if (!outputDir.exists()) {
                        log.error("目录创建失败: {}", outputPathDirectory);
                        throw new IOException("目录创建失败: " + outputPathDirectory);
                    }
                }
            }

            // 验证目录是否可写
            if (!outputDir.canWrite()) {
                log.error("目录没有写权限: {}", outputPathDirectory);
                throw new IOException("目录没有写权限: " + outputPathDirectory);
            }

            // 生成安全的输出文件路径
            String tempOutputPath = outputPathDirectory + "/" + safeStreamTitle + "_" + threadID
                    + "_temp.mp4";
            String finalOutputPath = outputPathDirectory + "/" + safeStreamTitle + "_" + threadID + ".mp4";

            // 检查文件写入权限（使用临时文件进行测试）
            File testFile = new File(outputPathDirectory + "/" + ".test_write_permission_" + threadID);
            try {
                if (testFile.exists() && !testFile.delete()) {
                    log.warn("无法删除已存在的测试文件: {}", testFile.getAbsolutePath());
                }
                if (!testFile.createNewFile()) {
                    log.error("文件创建测试失败，请检查写入权限: {}", testFile.getAbsolutePath());
                    throw new IOException("文件创建测试失败，请检查写入权限: " + testFile.getAbsolutePath());
                }
                Files.delete(testFile.toPath());
                log.debug("文件写入权限测试通过");
            } catch (IOException e) {
                log.error("文件写入权限测试失败: {}", e.getMessage());
                throw e;
            }

            // 验证视频流URL是否有效
            if (!isValidUrl(captureUrl)) {
                log.error("无效的视频流URL: {}", captureUrl);
                throw new IllegalArgumentException("无效的视频流URL: " + captureUrl);
            }

            log.info("开始采集视频流: {}, URL: {}", streamTitle, captureUrl);

            // 构建FFmpeg命令
            String command = buildOptimizedFFmpegCommand(captureUrl, tempOutputPath, videoStream);
            log.info("执行FFmpeg采集命令: {}", command);

            // 启动FFmpeg进程
            ProcessBuilder processBuilder = new ProcessBuilder();
            processBuilder.command(parseCommand(command));
            processBuilder.redirectErrorStream(true);
            Process process = processBuilder.start();

            // 创建进程信息对象
            ProcessInfo processInfo = new ProcessInfo(process, finalOutputPath, tempOutputPath, videoStream, threadID);
            processMap.put(threadID, processInfo);

            // 初始化最终化信息
            ProcessFinalizationInfo finalizationInfo = new ProcessFinalizationInfo(threadID, tempOutputPath,
                    finalOutputPath);
            finalizationMap.put(threadID, finalizationInfo);

            // 启动输出流读取线程
            executorService.execute(() -> readProcessOutput(process, threadID, streamTitle));

            // 启动文件监控线程
            executorService.execute(() -> monitorFileGrowth(threadID, processInfo, outputPathDirectory));

            // 启动进程监控线程
            executorService.execute(() -> monitorProcess(threadID, processInfo));

            log.info("视频采集任务已启动，线程ID: {}, 标题: {}, 临时文件: {}, 最终文件: {}",
                    threadID, streamTitle, tempOutputPath, finalOutputPath);
            return true;

        } catch (IOException e) {
            log.error("FFmpeg启动失败: {}", e.getMessage(), e);
            throw new RuntimeException("FFmpeg启动失败: " + e.getMessage(), e);
        }
    }

    private String buildOptimizedFFmpegCommand(String captureUrl, String outputPath, VideoStream videoStream) {
        String streamUrl = videoStream.getCaptureUrl().toLowerCase();
        StringBuilder command = new StringBuilder();

        try {
            // 确保FFmpeg路径正确，尤其是包含空格的情况
            command.append('"').append(ffmpegPath).append('"').append(" -y ");

            if (streamUrl.contains("rtsp")) {
                command.append("-rtsp_transport tcp ");
            }

            // 增加分析时长和探测大小，提高流兼容性
            command.append(String.format("-analyzeduration %d -probesize %d ",
                    analyzeDuration, probeSize));

            // 注意：已移除不被当前FFmpeg版本支持的reconnect相关参数
            // 如需自动重连功能，请使用支持这些参数的FFmpeg更新版本

            // 正确处理输入URL，确保特殊字符被正确解析
            String safeCaptureUrl = captureUrl;
            // 如果URL中包含空格但没有被引号包裹，添加引号
            if (captureUrl.contains(" ") && !captureUrl.startsWith("\"") && !captureUrl.endsWith("\"")) {
                safeCaptureUrl = '"' + captureUrl + '"';
            }
            command.append("-i ").append(safeCaptureUrl).append(" ");

            // 视频编码参数
            command.append(String.format("-c:v %s -preset %s -crf %d ", videoCodec, preset, crf));
            command.append(String.format("-g %d -keyint_min %d -sc_threshold %d ", gop, keyintMin, scThreshold));

            // 添加音频处理参数
            command.append("-flags +global_header -movflags faststart ");
            command.append(String.format("-c:a %s -strict experimental ", audioCodec));
            command.append("-ar 44100 -ac 2 "); // 设置音频采样率和声道数

            // 确保输出路径被正确引号包裹
            String safeOutputPath = outputPath;
            if (outputPath.contains(" ") && !outputPath.startsWith("\"") && !outputPath.endsWith("\"")) {
                safeOutputPath = '"' + outputPath + '"';
            }
            command.append("-f mp4 ").append(safeOutputPath);

            // 记录完整的FFmpeg命令用于调试
            log.debug("构建的FFmpeg命令: {}", command.toString());
            return command.toString();
        } catch (Exception e) {
            log.error("构建FFmpeg命令失败: {}", e.getMessage(), e);
            throw new RuntimeException("构建FFmpeg命令失败: " + e.getMessage(), e);
        }
    }

    /**
     * 解析命令字符串为命令数组，正确处理引号内的参数
     * 
     * @param command 完整的命令字符串
     * @return 解析后的命令数组
     */
    private String[] parseCommand(String command) {
        List<String> args = new ArrayList<>();
        StringBuilder currentArg = new StringBuilder();
        boolean inQuotes = false;
        boolean inDoubleQuotes = false;

        for (int i = 0; i < command.length(); i++) {
            char c = command.charAt(i);

            if (c == '\\' && i + 1 < command.length()) {
                // 处理转义字符
                currentArg.append(command.charAt(i + 1));
                i++;
                continue;
            }

            if (c == '"' && !inQuotes) {
                inDoubleQuotes = !inDoubleQuotes;
                continue;
            }

            if (c == '\'' && !inDoubleQuotes) {
                inQuotes = !inQuotes;
                continue;
            }

            if (c == ' ' && !inQuotes && !inDoubleQuotes) {
                // 遇到空格且不在引号内，参数结束
                if (currentArg.length() > 0) {
                    args.add(currentArg.toString());
                    currentArg.setLength(0);
                }
            } else {
                currentArg.append(c);
            }
        }

        // 添加最后一个参数
        if (currentArg.length() > 0) {
            args.add(currentArg.toString());
        }

        return args.toArray(new String[0]);
    }

    private void readProcessOutput(Process process, String threadID, String streamTitle) {
        try (java.util.Scanner scanner = new java.util.Scanner(process.getInputStream())) {
            while (scanner.hasNextLine()) {
                String line = scanner.nextLine();

                if (line.contains("error") || line.contains("Error") || line.contains("failed")) {
                    log.error("[{} {}] {}", streamTitle, threadID, line);
                } else if (line.contains("warning") || line.contains("Warning")) {
                    log.warn("[{} {}] {}", streamTitle, threadID, line);
                } else if (line.contains("frame=")) {
                    log.debug("[{} {}] {}", streamTitle, threadID, line);
                } else {
                    log.info("[{} {}] {}", streamTitle, threadID, line);
                }

                detectCriticalErrors(line, threadID);
            }
        } catch (Exception e) {
            log.warn("读取进程输出异常: {}", e.getMessage());
        }
    }

    private void detectCriticalErrors(String line, String threadID) {
        if (line.contains("Connection refused") ||
                line.contains("Invalid data found") ||
                line.contains("Server returned 404") ||
                line.contains("Unable to open resource") ||
                line.contains("missing picture in access unit") ||
                line.contains("No such file or directory") ||
                line.contains("Error opening input file")) {
            log.error("检测到关键错误，线程ID: {}, 错误信息: {}", threadID, line);
            // 获取对应的进程信息
            ProcessInfo processInfo = processMap.get(threadID);
            if (processInfo != null) {
                // 标记为最终化状态，尝试进行最终化处理
                if (!processInfo.isFinalizing) {
                    processInfo.isFinalizing = true;
                    log.info("由于关键错误，开始强制最终化: {}", threadID);
                    finalizeVideoFileAsync(processInfo);
                }
            }
        }
    }

//    private void monitorFileGrowth(String threadID, ProcessInfo processInfo, String outputPathDirectory) {
//
//        File outputFile = new File(processInfo.tempOutputPath);
//        long checkInterval = 5000;
//        int maxNoGrowthChecks = fileNoGrowthTimeout * 1000 / (int) checkInterval;
//        int noGrowthCount = 0;
//
//        try {
//            Thread.sleep(5000);
//
//            while (processInfo.process.isAlive() && !processInfo.isFinalizing) {
//                Thread.sleep(checkInterval);
//
//                if (!outputFile.exists()) {
//                    log.warn("输出文件不存在: {}", processInfo.tempOutputPath);
//                    noGrowthCount++;
//
//                    if (noGrowthCount >= 3) {
//                        handleFileCreationFailure(threadID, processInfo);
//                        break;
//                    }
//                    continue;
//                }
//
//                long currentSize = outputFile.length();
//                long currentModified = outputFile.lastModified();
//
//                if (currentSize == processInfo.lastFileSize &&
//                        currentModified == processInfo.lastModifiedTime) {
//                    noGrowthCount++;
//                    log.warn("文件 {} 已 {} 秒未增长",
//                            processInfo.tempOutputPath, noGrowthCount * checkInterval / 1000);
//
//                    if (noGrowthCount >= maxNoGrowthChecks) {
//                        handleNoGrowthTimeout(threadID, processInfo, outputPathDirectory);
//
//                        break;
//                    }
//                } else {
//                    noGrowthCount = 0;
//                    processInfo.lastFileSize = currentSize;
//                    processInfo.lastModifiedTime = currentModified;
//
//                    if (currentSize % (1024 * 1024) == 0) {
//                        log.debug("文件正常增长，当前大小: {} MB", currentSize / (1024 * 1024));
//                    }
//                }
//            }
//        } catch (Exception e) {
//            log.error("文件监控异常: {}", e.getMessage(), e);
//        }
//    }

    private void monitorFileGrowth(String threadID, ProcessInfo processInfo, String outputPathDirectory) {
        File outputFile = new File(processInfo.tempOutputPath);
        // 1. 关键：获取文件的父目录（存储临时文件的目录）
        File parentDir = outputFile.getParentFile();
        long checkInterval = 5000;
        int maxNoGrowthChecks = fileNoGrowthTimeout > 0 ? (fileNoGrowthTimeout * 1000 / (int) checkInterval) : 12; // 默认60秒超时
        int noGrowthCount = 0;

        try {
            // 2. 初始等待：给FFmpeg启动和目录创建的缓冲时间
            Thread.sleep(5000);

            while (processInfo.process.isAlive() && !processInfo.isFinalizing) {
                Thread.sleep(checkInterval);

                // -------------------------- 新增：目录容错处理 --------------------------
                if (parentDir == null) {
                    log.error("文件父目录为空（无效路径）: {}", processInfo.tempOutputPath);
                    noGrowthCount++;
                    if (noGrowthCount >= 3) {
                        handleFileCreationFailure(threadID, processInfo);
                        break;
                    }
                    continue;
                }

                // 若父目录不存在，尝试创建（补救措施）
                if (!parentDir.exists()) {
                    log.warn("文件父目录不存在，尝试创建: {}", parentDir.getAbsolutePath());
                    boolean dirCreated = parentDir.mkdirs(); // mkdirs()创建多级目录
                    if (dirCreated) {
                        log.info("父目录创建成功: {}", parentDir.getAbsolutePath());
                    } else {
                        log.error("父目录创建失败（可能无权限）: {}", parentDir.getAbsolutePath());
                        noGrowthCount++;
                        if (noGrowthCount >= 3) {
                            handleFileCreationFailure(threadID, processInfo);
                            break;
                        }
                        continue;
                    }
                }

                // 若父目录存在，但无写入权限（也会导致FFmpeg无法创建文件）
                if (!parentDir.canWrite()) {
                    log.error("父目录无写入权限: {}", parentDir.getAbsolutePath());
                    noGrowthCount++;
                    if (noGrowthCount >= 3) {
                        handleFileCreationFailure(threadID, processInfo);
                        break;
                    }
                    continue;
                }
                // -------------------------- 目录容错处理结束 --------------------------

                // 原有逻辑：检查文件是否存在
                if (!outputFile.exists()) {
                    log.warn("输出文件不存在: {}", processInfo.tempOutputPath);
                    noGrowthCount++;

                    if (noGrowthCount >= 3) {
                        handleFileCreationFailure(threadID, processInfo);
                        break;
                    }
                    continue;
                }

                // 原有逻辑：检查文件是否增长
                long currentSize = outputFile.length();
                long currentModified = outputFile.lastModified();

                if (currentSize == processInfo.lastFileSize && currentModified == processInfo.lastModifiedTime) {
                    noGrowthCount++;
                    log.warn("文件 {} 已 {} 秒未增长",
                            processInfo.tempOutputPath, noGrowthCount * checkInterval / 1000);

                    if (noGrowthCount >= maxNoGrowthChecks) {
                        handleNoGrowthTimeout(threadID, processInfo, outputPathDirectory);
                        break;
                    }
                } else {
                    noGrowthCount = 0;
                    processInfo.lastFileSize = currentSize;
                    processInfo.lastModifiedTime = currentModified;

                    if (currentSize % (1024 * 1024) <= 1024) { // 允许1KB误差，避免漏打日志
                        log.debug("文件正常增长，当前大小: {:.1f} MB", currentSize / (1024.0 * 1024));
                    }
                }
            }
        } catch (InterruptedException e) {
            log.warn("文件监控线程被中断（threadID: {}）", threadID, e);
            Thread.currentThread().interrupt(); // 恢复中断状态
            processInfo.isFinalizing = true;
        } catch (Exception e) {
            log.error("文件监控异常（threadID: {}, 路径: {}）", threadID, processInfo.tempOutputPath, e);
        }
    }

    private void handleFileCreationFailure(String threadID, ProcessInfo processInfo) {
        log.error("文件创建失败，停止采集任务: {}", threadID);
        processInfo.process.destroy();
        processMap.remove(threadID);
        finalizationMap.remove(threadID);
        // cleanupTempFile(processInfo.tempOutputPath);
    }

    private void handleNoGrowthTimeout(String threadID, ProcessInfo processInfo, String outputPathDirectory) {

        log.error("文件超过 {} 秒未增长，尝试重新启动采集", fileNoGrowthTimeout);
        processInfo.process.destroy();

        if (processInfo.retryCount < maxRetryCount) {
            processInfo.retryCount++;
            log.info("尝试第 {} 次重新启动采集: {}", processInfo.retryCount, threadID);
            // 清理旧临时文件，避免使用不完整文件
            // cleanupTempFile(processInfo.tempOutputPath);
            // 重新启动采集
            ffmpegStartSave(processInfo.videoStream, outputPathDirectory);
        } else {
            // 达到最大重试次数后再进入最终化
            log.error("已达到最大重试次数({})，进入最终化: {}", maxRetryCount, threadID);
            processInfo.isFinalizing = true;
            finalizeVideoFileAsync(processInfo);
        }
    }

    private void cleanupTempFile(String filePath) {
        // try {
        // // 等待一小段时间确保文件句柄被释放
        // Thread.sleep(1000);
        // File file = new File(filePath);
        // if (file.exists()) {
        // boolean deleted = Files.deleteIfExists(file.toPath());
        // if (deleted) {
        // log.info("已清理临时文件: {}", filePath);
        // } else {
        // log.warn("临时文件存在但无法删除: {}", filePath);
        // }
        // } else {
        // log.debug("临时文件不存在，无需清理: {}", filePath);
        // }
        // } catch (IOException e) {
        // log.warn("清理临时文件失败: {}", e.getMessage());
        // } catch (InterruptedException e) {
        // Thread.currentThread().interrupt();
        // log.warn("清理临时文件过程被中断: {}", e.getMessage());
        // }
    }

    private void monitorProcess(String threadID, ProcessInfo processInfo) {
        try {
            int exitCode = processInfo.process.waitFor();
            log.info("FFmpeg进程 {} 退出，退出码: {}", threadID, exitCode);

            processInfo.isFinalizing = true;
            updateFinalizationState(threadID, ProcessFinalizationState.PROCESSING, "进程退出，开始最终化", 0);

            // 异步执行最终化
            finalizeVideoFileAsync(processInfo);

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.warn("监控线程被中断: {}", threadID);
            // 强制销毁进程
            if (processInfo.process.isAlive()) {
                processInfo.process.destroyForcibly();
            }
            updateFinalizationState(threadID, ProcessFinalizationState.FAILED, "监控线程被中断", 0);
        } finally {
            closeProcessStreams(processInfo.process);
        }
    }

    /**
     * 带进度反馈的最终化执行
     */
    private boolean executeFinalizationWithProgress(ProcessInfo processInfo, String threadID) {
        try {
            // String finalizeCommand = String.format(
            // "%s -y -i \"%s\" -c copy -movflags +faststart \"%s\"",
            // ffmpegPath, processInfo.tempOutputPath, processInfo.outputPath);
            // 替换finalizeCommand，使用重新编码而非流复制
            String finalizeCommand = String.format(
                    "%s -y -i \"%s\" -c:v %s -c:a %s -movflags +faststart \"%s\"",
                    ffmpegPath, processInfo.tempOutputPath, videoCodec, audioCodec, processInfo.outputPath);
            Process finalizeProcess = Runtime.getRuntime().exec(finalizeCommand);

            // 启动进度监控
            startProgressMonitoring(finalizeProcess, threadID);

            int exitCode = finalizeProcess.waitFor();

            if (exitCode == 0) {
                if (isValidMp4File(processInfo.outputPath)) {
                    cleanupTempFile(processInfo.tempOutputPath);
                    updateFinalizationState(threadID, ProcessFinalizationState.COMPLETED,
                            "最终化成功", 100);
                    return true;
                } else {
                    updateFinalizationState(threadID, ProcessFinalizationState.FAILED,
                            "最终化后文件验证失败", 100);
                    return false;
                }
            } else {
                updateFinalizationState(threadID, ProcessFinalizationState.FAILED,
                        "重封装失败，退出码: " + exitCode, 100);
                return false;
            }
        } catch (Exception e) {
            updateFinalizationState(threadID, ProcessFinalizationState.FAILED,
                    "重封装异常: " + e.getMessage(), 100);
            return false;
        }
    }

    /**
     * 启动进度监控
     */
    private void startProgressMonitoring(Process process, String threadID) {
        executorService.execute(() -> {
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(process.getInputStream()))) {

                String line;
                while ((line = reader.readLine()) != null) {
                    double progress = parseProgressFromOutput(line);
                    if (progress >= 0) {
                        updateFinalizationProgress(threadID, progress);
                    }
                }
            } catch (IOException e) {
                log.warn("进度监控异常: {}", e.getMessage());
            }
        });
    }

    /**
     * 从FFmpeg输出解析进度
     */
    private double parseProgressFromOutput(String line) {
        Pattern timePattern = Pattern.compile("time=(\\d+):(\\d+):(\\d+\\.\\d+)");
        Matcher matcher = timePattern.matcher(line);

        if (matcher.find()) {
            try {
                int hours = Integer.parseInt(matcher.group(1));
                int minutes = Integer.parseInt(matcher.group(2));
                double seconds = Double.parseDouble(matcher.group(3));

                double totalSeconds = hours * 3600 + minutes * 60 + seconds;
                // 简化进度计算：假设30分钟视频为100%
                double estimatedProgress = Math.min(100, (totalSeconds / 1800) * 100);
                return estimatedProgress;
            } catch (NumberFormatException e) {
                return -1;
            }
        }
        return -1;
    }

    /**
     * 更新最终化进度
     */
    private void updateFinalizationProgress(String threadID, double progress) {
        ProcessFinalizationInfo info = finalizationMap.get(threadID);
        if (info != null && info.state == ProcessFinalizationState.PROCESSING) {
            info.progress = Math.min(100, Math.max(info.progress, progress));
        }
    }

    /**
     * 关闭进程流
     */
    private void closeProcessStreams(Process process) {
        try {
            process.getInputStream().close();
        } catch (IOException e) {
            log.warn("关闭输入流异常: {}", e.getMessage());
        }
        try {
            process.getOutputStream().close();
        } catch (IOException e) {
            log.warn("关闭输出流异常: {}", e.getMessage());
        }
        try {
            process.getErrorStream().close();
        } catch (IOException e) {
            log.warn("关闭错误流异常: {}", e.getMessage());
        }
    }

    @Override
    public AjaxResult stopSave(Long dataId) {

        try {
            if (processMap.isEmpty()) {
                return AjaxResult.error("当前没有运行中的采集任务");
            }

            VideoData videoData = videoDataService.selectVideoDataByDataId(dataId);
            if (videoData == null) {
                return AjaxResult.error("数据不存在");
            }
            videoData.setEndCollectTime(LocalDateTime.now());
            long durationMillis = java.time.Duration
                    .between(videoData.getStartCollectTime(), videoData.getEndCollectTime()).toMillis();
            long seconds = durationMillis / 1000;
            long hours = seconds / 3600;
            long minutes = (seconds % 3600) / 60;
            seconds = seconds % 60;
            String durationStr = String.format("%02d:%02d:%02d", hours, minutes, seconds);
            videoData.setRemark(videoData.getRemark() + "时长:" + durationStr);

            videoDataService.updateVideoData(videoData);

            int count = processMap.size();
            Set<String> threadIDs = new HashSet<>(processMap.keySet());

            for (String threadID : threadIDs) {
                ProcessInfo processInfo = processMap.get(threadID);
                if (processInfo != null && processInfo.process.isAlive()) {
                    try {
                        processInfo.process.getOutputStream().write("q\n".getBytes());
                        processInfo.process.getOutputStream().flush();
                        log.info("已发送退出命令到FFmpeg进程: {}", threadID);
                    } catch (IOException e) {
                        log.warn("无法发送退出命令到进程: {}", threadID);
                    }

                    if (!processInfo.process.waitFor(10, TimeUnit.SECONDS)) {
                        processInfo.process.destroyForcibly();
                    }
                }

                if (processInfo != null && !processInfo.isFinalizing) {
                    processInfo.isFinalizing = true;
                    updateFinalizationState(threadID, ProcessFinalizationState.PROCESSING, "手动停止，开始最终化", 0);
                    finalizeVideoFileAsync(processInfo);
                }
            }

            return AjaxResult.success("已停止全部" + count + "个采集任务，最终化进行中");
        } catch (Exception e) {
            log.error("批量停止失败", e);
            return AjaxResult.error("批量停止失败: " + e.getMessage());
        }
    }

    /**
     * 优雅停止单个任务
     */
    public AjaxResult stopSingleSave(String threadID) {
        try {
            ProcessInfo processInfo = processMap.get(threadID);
            if (processInfo == null) {
                return AjaxResult.error("未找到该采集任务");
            }

            if (processInfo.process.isAlive()) {
                try {
                    processInfo.process.getOutputStream().write("q\n".getBytes());
                    processInfo.process.getOutputStream().flush();
                } catch (IOException e) {
                    log.warn("无法发送退出命令到进程: {}", threadID);
                }

                if (!processInfo.process.waitFor(10, TimeUnit.SECONDS)) {
                    processInfo.process.destroyForcibly();
                }
            }

            if (!processInfo.isFinalizing) {
                processInfo.isFinalizing = true;
                updateFinalizationState(threadID, ProcessFinalizationState.PROCESSING, "手动停止，开始最终化", 0);
                finalizeVideoFileAsync(processInfo);
            }

            return AjaxResult.success("成功停止采集任务，最终化进行中");

        } catch (Exception e) {
            log.error("停止单个任务失败", e);
            return AjaxResult.error("停止采集失败: " + e.getMessage());
        }
    }

    /**
     * 获取最终化状态
     */
    @Override
    public AjaxResult getFinalizationStatus(String threadID) {
        ProcessFinalizationInfo info = finalizationMap.get(threadID);
        if (info == null) {
            // 记录已删除，返回已完成或未知状态
            return AjaxResult.success(createFinalizationStatus(threadID, "COMPLETED_OR_UNKNOWN",
                    "记录已清理，最终化可能已完成", 100, null, null, null, null));
        }

        Map<String, Object> result = new HashMap<>();
        result.put("threadID", threadID);
        result.put("state", info.state.toString());
        result.put("message", info.message);
        result.put("progress", info.progress);
        result.put("startTime", info.startTime);
        result.put("endTime", info.endTime);
        result.put("tempFilePath", info.tempFilePath);
        result.put("finalFilePath", info.finalFilePath);

        return AjaxResult.success(result);
    }

    private Map<String, Object> createFinalizationStatus(String threadID, String state,
            String message, double progress, LocalDateTime startTime,
            LocalDateTime endTime, String tempPath, String finalPath) {
        Map<String, Object> status = new HashMap<>();
        status.put("threadID", threadID);
        status.put("state", state);
        status.put("message", message);
        status.put("progress", progress);
        status.put("startTime", startTime);
        status.put("endTime", endTime);
        status.put("tempFilePath", tempPath);
        status.put("finalFilePath", finalPath);
        return status;
    }

    /**
     * 批量获取最终化状态
     */
    @Override
    public AjaxResult getBatchFinalizationStatus(List<String> threadIDs) {
        Map<String, Object> result = new HashMap<>();

        for (String threadID : threadIDs) {
            ProcessFinalizationInfo info = finalizationMap.get(threadID);
            if (info != null) {
                Map<String, Object> status = new HashMap<>();
                status.put("state", info.state.toString());
                status.put("message", info.message);
                status.put("progress", info.progress);
                status.put("startTime", info.startTime);
                status.put("endTime", info.endTime);
                status.put("tempFilePath", info.tempFilePath);
                status.put("finalFilePath", info.finalFilePath);

                result.put(threadID, status);
            } else {
                result.put(threadID, "未找到最终化信息");
            }
        }

        return AjaxResult.success(result);
    }

    /**
     * 获取当前运行的任务数量
     */
    public int getRunningTaskCount() {
        return processMap.size();
    }

    /**
     * 获取所有运行中的任务ID
     */
    public Set<String> getRunningTaskIds() {
        return new HashSet<>(processMap.keySet());
    }

    /**
     * 获取所有最终化任务ID
     */
    @Override
    public Set<String> getFinalizationTaskIds() {
        return new HashSet<>(finalizationMap.keySet());
    }

    /**
     * 获取任务状态信息
     */
    public String getTaskStatus(String threadID) {
        ProcessInfo processInfo = processMap.get(threadID);
        ProcessFinalizationInfo finalizationInfo = finalizationMap.get(threadID);

        StringBuilder status = new StringBuilder();

        if (processInfo != null) {
            status.append("进程状态: ").append(processInfo.process.isAlive() ? "运行中" : "已停止").append("\n");
            status.append("重试次数: ").append(processInfo.retryCount).append("\n");
            status.append("最终化状态: ").append(processInfo.isFinalizing ? "进行中" : "未开始").append("\n");
            status.append("临时文件: ").append(processInfo.tempOutputPath).append("\n");
            status.append("最终文件: ").append(processInfo.outputPath).append("\n");

            File tempFile = new File(processInfo.tempOutputPath);
            File finalFile = new File(processInfo.outputPath);

            if (tempFile.exists()) {
                status.append("临时文件大小: ").append(tempFile.length() / 1024 / 1024).append(" MB\n");
            }
            if (finalFile.exists()) {
                status.append("最终文件大小: ").append(finalFile.length() / 1024 / 1024).append(" MB\n");
            }
        }

        if (finalizationInfo != null) {
            status.append("最终化状态: ").append(finalizationInfo.state.toString()).append("\n");
            status.append("最终化进度: ").append(finalizationInfo.progress).append("%\n");
            status.append("最终化消息: ").append(finalizationInfo.message).append("\n");
        }

        return status.toString();
    }

    /**
     * 清理过期的最终化记录（保留7天）
     */
    public void cleanupOldFinalizationRecords() {
        LocalDateTime sevenDaysAgo = LocalDateTime.now().minusDays(7);
        Iterator<Map.Entry<String, ProcessFinalizationInfo>> iterator = finalizationMap.entrySet().iterator();

        int removedCount = 0;
        while (iterator.hasNext()) {
            Map.Entry<String, ProcessFinalizationInfo> entry = iterator.next();
            ProcessFinalizationInfo info = entry.getValue();

            if (info.endTime != null && info.endTime.isBefore(sevenDaysAgo)) {
                iterator.remove();
                removedCount++;
            }
        }

        log.info("清理了 {} 条过期的最终化记录", removedCount);
    }

    /**
     * 尝试直接重命名文件
     */
    private boolean attemptDirectRename(ProcessInfo processInfo) {
        try {
            File tempFile = new File(processInfo.tempOutputPath);
            File finalFile = new File(processInfo.outputPath);

            // 如果目标文件已存在，先删除
            if (finalFile.exists()) {
                // if (!finalFile.delete()) {
                // log.warn("无法删除已存在的最终文件: {}", processInfo.outputPath);
                // return false;
                // }
                return true;
            }

            // 执行重命名
            boolean renameSuccess = tempFile.renameTo(finalFile);

            if (renameSuccess) {
                log.info("直接重命名文件成功: {} -> {}", processInfo.tempOutputPath, processInfo.outputPath);

                // 验证重命名后的文件
                if (finalFile.exists() && finalFile.length() > 0) {
                    log.info("重命名后文件验证成功，大小: {} MB", finalFile.length() / (1024 * 1024));
                    return true;
                } else {
                    log.warn("重命名后文件验证失败，文件不存在或为空");
                    return false;
                }
            } else {
                log.warn("重命名操作返回失败");

                // 尝试使用Files.move作为备选方案
                try {
                    Files.move(Paths.get(processInfo.tempOutputPath),
                            Paths.get(processInfo.outputPath),
                            StandardCopyOption.REPLACE_EXISTING);

                    if (finalFile.exists() && finalFile.length() > 0) {
                        log.info("使用Files.move重命名成功: {}", processInfo.outputPath);
                        return true;
                    }
                } catch (IOException e) {
                    log.warn("Files.move重命名也失败: {}", e.getMessage());
                }

                return false;
            }

        } catch (Exception e) {
            log.warn("直接重命名文件失败: {}", e.getMessage());
            return false;
        }
    }

    /**
     * 最终化视频文件 - 修复状态同步问题
     */
    private boolean finalizeVideoFile(ProcessInfo processInfo) {
        String threadID = processInfo.threadID;

        try {
            File tempFile = new File(processInfo.tempOutputPath);
            if (!tempFile.exists() || tempFile.length() < 1024 * 1024) {
                updateFinalizationState(threadID, ProcessFinalizationState.FAILED,
                        "临时文件不存在或太小", 0);
                return false;
            }

            // 检查是否已经是有效的MP4文件
            boolean isValid = isValidMp4File(processInfo.tempOutputPath);
            log.info("文件有效性检查结果: {}, 文件: {}", isValid, processInfo.tempOutputPath);

            if (isValid) {
                updateFinalizationState(threadID, ProcessFinalizationState.PROCESSING,
                        "文件验证通过，尝试重命名", 50);

                boolean renameSuccess = attemptDirectRename(processInfo);

                if (renameSuccess) {
                    // 再次验证最终文件
                    if (isValidMp4File(processInfo.outputPath)) {
                        updateFinalizationState(threadID, ProcessFinalizationState.COMPLETED,
                                "最终化完成", 100);
                        log.info("最终化完全成功: {}", processInfo.outputPath);
                        return true;
                    } else {
                        updateFinalizationState(threadID, ProcessFinalizationState.FAILED,
                                "重命名后文件验证失败", 100);
                        log.warn("重命名后文件验证失败: {}", processInfo.outputPath);
                        return false;
                    }
                } else {
                    updateFinalizationState(threadID, ProcessFinalizationState.FAILED,
                            "重命名失败", 100);
                    log.warn("重命名操作失败: {} -> {}", processInfo.tempOutputPath, processInfo.outputPath);
                    return false;
                }
            }

            // 需要重封装
            updateFinalizationState(threadID, ProcessFinalizationState.PROCESSING,
                    "开始重封装文件", 10);

            return executeFinalizationWithProgress(processInfo, threadID);

        } catch (Exception e) {
            log.error("最终化过程异常: {}", e.getMessage(), e);
            updateFinalizationState(threadID, ProcessFinalizationState.FAILED,
                    "最终化异常: " + e.getMessage(), 0);
            return false;
        }
    }

    /**
     * 验证MP4文件是否有效 - 增强版本
     */
    private boolean isValidMp4File(String filePath) {
        try {
            File file = new File(filePath);
            if (!file.exists() || file.length() == 0) {
                log.warn("文件不存在或为空: {}", filePath);
                return false;
            }

            // 简单的文件头检查
            try (FileInputStream fis = new FileInputStream(file)) {
                byte[] header = new byte[8];
                if (fis.read(header) == 8) {
                    // 检查MP4文件头 (ftyp)
                    if (header[4] == 'f' && header[5] == 't' && header[6] == 'y' && header[7] == 'p') {
                        log.debug("文件头验证通过: {}", filePath);
                        return true;
                    }
                }
            }

            // 使用FFmpeg进行详细验证
            String checkCommand = String.format("%s -v error -i \"%s\" -f null -", ffmpegPath, filePath);
            Process checkProcess = Runtime.getRuntime().exec(checkCommand);

            // 读取错误输出以获取更多信息
            StringBuilder errorOutput = new StringBuilder();
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(checkProcess.getErrorStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    errorOutput.append(line).append("\n");
                }
            }

            int exitCode = checkProcess.waitFor();

            if (exitCode == 0) {
                log.info("FFmpeg验证成功: {}", filePath);
                return true;
            } else {
                log.warn("FFmpeg验证失败: {}, 错误信息: {}", filePath, errorOutput.toString());
                return false;
            }

        } catch (Exception e) {
            log.warn("MP4文件验证异常: {}, 文件: {}", e.getMessage(), filePath);
            return false;
        }
    }

    /**
     * 更新最终化状态 - 添加详细日志
     */
    private void updateFinalizationState(String threadID, ProcessFinalizationState state,
            String message, double progress) {
        ProcessFinalizationInfo info = finalizationMap.get(threadID);
        if (info != null) {
            info.state = state;
            info.message = message;
            info.progress = Math.min(100, Math.max(0, progress));

            if (state == ProcessFinalizationState.PROCESSING && info.startTime == null) {
                info.startTime = LocalDateTime.now();
            }

            if (state == ProcessFinalizationState.COMPLETED ||
                    state == ProcessFinalizationState.FAILED) {
                info.endTime = LocalDateTime.now();
                // 记录最终状态详情
                log.info("最终化任务完成: threadID={}, state={}, message={}, progress={}%",
                        threadID, state, message, progress);
            }

            log.debug("最终化状态更新: {} - {} - {}%", threadID, message, progress);
        } else {
            log.warn("未找到最终化信息: {}", threadID);
        }
    }

    /**
     * 添加重试机制到最终化过程
     */
    private boolean finalizeVideoFileWithRetry(ProcessInfo processInfo, int maxRetries) {
        String threadID = processInfo.threadID;

        for (int attempt = 1; attempt <= maxRetries; attempt++) {
            try {
                updateFinalizationState(threadID, ProcessFinalizationState.PROCESSING,
                        String.format("最终化尝试 %d/%d", attempt, maxRetries), 0);

                boolean result = finalizeVideoFile(processInfo);

                if (result) {
                    handleFinalizationSuccess(threadID, processInfo);
                    return true;
                }

                if (attempt < maxRetries) {
                    log.warn("最终化尝试 {} 失败，{}秒后重试", attempt, attempt * 2);
                    Thread.sleep(attempt * 2000); // 指数退避
                }

            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                updateFinalizationState(threadID, ProcessFinalizationState.FAILED,
                        "最终化被中断", 0);
                return false;
            } catch (Exception e) {
                log.error("最终化过程异常: {}", e.getMessage(), e);
            }
        }

        updateFinalizationState(threadID, ProcessFinalizationState.FAILED,
                "最终化重试次数耗尽", 100);
        return false;
    }

    // 修改异步最终化方法使用重试机制
    private void finalizeVideoFileAsync(ProcessInfo processInfo) {
        CompletableFuture<Boolean> future = CompletableFuture.supplyAsync(() -> {
            return finalizeVideoFileWithRetry(processInfo, 3); // 最多重试3次
        }, finalizationExecutor);

        processInfo.finalizationFuture = future;

        future.whenComplete((result, ex) -> {
            String threadID = processInfo.threadID;

            if (ex != null) {
                log.error("最终化过程异常: {}", ex.getMessage(), ex);
                // 异常情况也立即清理
                handleFinalizationException(threadID, processInfo, ex);
                return;
            }

            if (result) {
                // 成功完成，立即清理
                handleFinalizationSuccess(threadID, processInfo);
            } else {
                // 失败完成，立即清理
                handleFinalizationFailure(threadID, processInfo);
            }
        });
    }

    /**
     * 最终化成功处理 - 立即删除记录
     */
    private void handleFinalizationSuccess(String threadID, ProcessInfo processInfo) {
        log.info("最终化成功完成: {}", threadID);

        // 记录最终状态（可选，如果需要日志的话）
        updateFinalizationState(threadID, ProcessFinalizationState.COMPLETED,
                "最终化成功", 100);

        // 立即清理记录
        cleanupFinalizationRecords(threadID);

        // 清理进程记录
        processMap.remove(threadID);
    }

    /**
     * 最终化失败处理 - 立即删除记录
     */
    private void handleFinalizationFailure(String threadID, ProcessInfo processInfo) {
        log.warn("最终化失败: {}", threadID);

        // 记录失败状态（可选）
        updateFinalizationState(threadID, ProcessFinalizationState.FAILED,
                "最终化失败", 100);

        // 立即清理记录
        cleanupFinalizationRecords(threadID);

        // 清理进程记录
        processMap.remove(threadID);

        // 清理临时文件
        // cleanupTempFile(processInfo.tempOutputPath);
    }

    /**
     * 最终化异常处理 - 立即删除记录
     */
    private void handleFinalizationException(String threadID, ProcessInfo processInfo, Throwable ex) {
        log.error("最终化过程异常: {}", ex.getMessage(), ex);

        // 记录异常状态（可选）
        updateFinalizationState(threadID, ProcessFinalizationState.FAILED,
                "最终化异常: " + ex.getMessage(), 0);

        // 立即清理记录
        cleanupFinalizationRecords(threadID);

        // 清理进程记录
        processMap.remove(threadID);

        // 清理临时文件
        // cleanupTempFile(processInfo.tempOutputPath);
    }

    /**
     * 立即清理最终化记录
     */
    private void cleanupFinalizationRecords(String threadID) {
        // 立即删除最终化记录
        // 只删除当前threadID的记录，保留其他线程的信息
        finalizationMap.remove(threadID);
        log.debug("已清理最终化记录: {}", threadID);
    }
}